package org.unidata.mdm.meta.util;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.unidata.mdm.core.context.DataRecordContext;
import org.unidata.mdm.core.type.data.ArrayAttribute;
import org.unidata.mdm.core.type.data.Attribute;
import org.unidata.mdm.core.type.data.CodeAttribute;
import org.unidata.mdm.core.type.data.DataRecord;
import org.unidata.mdm.core.type.data.SimpleAttribute;
import org.unidata.mdm.core.type.model.AttributeElement;
import org.unidata.mdm.core.type.model.EntityElement;
import org.unidata.mdm.core.type.model.support.AttributeValueGenerator;
import org.unidata.mdm.core.type.model.support.ExternalIdValueGenerator;
import org.unidata.mdm.meta.configuration.MetaConfiguration;
import org.unidata.mdm.meta.exception.MetaExceptionIds;
import org.unidata.mdm.meta.exception.ModelRuntimeException;
import org.unidata.mdm.meta.service.impl.CustomValueGeneratingComponent;
import org.unidata.mdm.meta.service.impl.data.instance.AttributeImpl;
import org.unidata.mdm.meta.service.impl.data.instance.LookupImpl;
import org.unidata.mdm.meta.service.impl.data.instance.RegisterImpl;
import org.unidata.mdm.meta.type.model.strategy.ConcatenatedValueGenerationStrategy;
import org.unidata.mdm.system.util.IdUtils;

public class ValueGeneratingUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(ValueGeneratingUtils.class);

    private static CustomValueGeneratingComponent customValueGeneratingComponent;

    /**
     * Disable instances.
     */
    private ValueGeneratingUtils() {
        super();
    }
    /**
     * Init from module.
     */
    public static void init() {
        customValueGeneratingComponent = MetaConfiguration.getBean(CustomValueGeneratingComponent.class);
    }
    /**
     * Applies CONCAT strategy to an attribute..
     */
    public static final AttributeValueGenerator CONCAT_ATTRIBUTE_GENERATOR
        = new AttributeValueGenerator() {
            /**
             * {@inheritDoc}
             */
            @Override
            public String generate(AttributeElement attribute, DataRecordContext input) {

                // 1. Strategy. Holds atrrs and separator.
                // We can cast it in this rough fashion because we rely on attributes pre-processing.
                ConcatenatedValueGenerationStrategy strategy
                    = (ConcatenatedValueGenerationStrategy) ((AttributeImpl) attribute).getAttribute()
                        .getValueGenerationStrategy();

                // 2. Record
                DataRecord record = input.getRecord();

                Object[] values = new Object[strategy.getAttributes().size()];
                for (int i = 0; i < strategy.getAttributes().size(); i++) {

                    String attrName = strategy.getAttributes().get(i);

                    // Only first level attrs are allowed
                    Object val = null;
                    Attribute attr = record != null ? record.getAttribute(attrName) : null;
                    if (attr != null) {
                        switch (attr.getAttributeType()) {
                        case SIMPLE:
                            val = ((SimpleAttribute<?>) attr).getValue();
                            break;
                        case CODE:
                            val = ((CodeAttribute<?>) attr).getValue();
                            break;
                        case ARRAY:
                            val = StringUtils.join(((ArrayAttribute<?>) attr).toArray(), strategy.getSeparator());
                            break;
                        default:
                            break;
                        }
                    }

                    if (val == null || StringUtils.isBlank(val.toString())) {
                        final String message = "Unable to generate code attribute value, using autogeneration strategy for entity [{}]. "
                                + "Either no data was given or content for configured fields is missing or incomplete. Processing [{}].";
                        throw new ModelRuntimeException(message, MetaExceptionIds.EX_META_CANNOT_APPLY_ATTRIBUTE_CONCAT_STRATEGY,
                                attribute.getContainer().getName(), attrName);
                    }

                    values[i] = val;
                }

                return StringUtils.join(values, strategy.getSeparator());
            }
            /**
             * {@inheritDoc}
             */
            @Override
            public Class<String> getOutputType() {
                return String.class;
            }
        };

    /**
     * Applies CONCAT strategy to an entity.
     */
    public static final ExternalIdValueGenerator CONCAT_ENTITY_GENERATOR
        = (EntityElement entity, DataRecordContext input) -> {

            // 1. Strategy. Holds atrrs and separator.
            // We can cast it in this rough fashion because we rely on attributes pre-processing.
            ConcatenatedValueGenerationStrategy strategy
                = (ConcatenatedValueGenerationStrategy)
                    (entity.isLookup()
                            ? ((LookupImpl) entity).getSource().getExternalIdGenerationStrategy()
                            : ((RegisterImpl) entity).getSource().getExternalIdGenerationStrategy());

            // 2. Record
            DataRecord record = input.getRecord();

            Object[] values = new Object[strategy.getAttributes().size()];
            for (int i = 0; i < strategy.getAttributes().size(); i++) {

                String attrName = strategy.getAttributes().get(i);

                // Only first level attrs are allowed
                Object val = null;
                Attribute attr = record != null ? record.getAttribute(attrName) : null;
                if (attr != null) {
                    switch (attr.getAttributeType()) {
                    case SIMPLE:
                        val = ((SimpleAttribute<?>) attr).getValue();
                        break;
                    case CODE:
                        val = ((CodeAttribute<?>) attr).getValue();
                        break;
                    case ARRAY:
                        val = StringUtils.join(((ArrayAttribute<?>) attr).toArray(), strategy.getSeparator());
                        break;
                    default:
                        break;
                    }
                }

                if (val == null || StringUtils.isBlank(val.toString())) {
                    final String message = "Unable to generate externalId value, using CONCAT autogeneration strategy for entity [{}]. "
                            + "Either no data was given or content for configured fields is missing or incomplete. Processing [{}].";
                    throw new ModelRuntimeException(message, MetaExceptionIds.EX_META_CANNOT_APPLY_ENTITY_CONCAT_STRATEGY,
                            entity.getName(), attrName);
                }

                values[i] = val;
            }

            return StringUtils.join(values, strategy.getSeparator());
        };
    /**
     * RANDOM attribute strategy.
     */
    public static final AttributeValueGenerator RANDOM_ATTRIBUTE_GENERATOR
        = new AttributeValueGenerator() {
            /**
             * {@inheritDoc}
             */
            @Override
            public String generate(AttributeElement attribute, DataRecordContext input) {
                return IdUtils.v1String();
            }
            /**
             * {@inheritDoc}
             */
            @Override
            public Class<String> getOutputType() {
                return String.class;
            }
        };
    /**
     * RANDOM entity strategy.
     */
    public static final ExternalIdValueGenerator RANDOM_ENTITY_GENERATOR
        = (EntityElement entity, DataRecordContext input) -> IdUtils.v1String();
    /**
     * Loads and defines generator class.
     * @param className the name of the class
     * @return generator instance
     */
    public static AttributeValueGenerator defineAttributeCustomValueGenerator(String className) {

        try {
            Class<?> klass = Thread.currentThread().getContextClassLoader().loadClass(className);
            return customValueGeneratingComponent.defineAttributeCustomValueGenerator(klass);
        } catch (ClassNotFoundException e) {
            LOGGER.error("Failed to load attribute custom value generating class for name [{}].", className, e);
        }

        return null;
    }
    /**
     * Loads and defines generator class.
     * @param className the name of the class
     * @return generator instance
     */
    public static ExternalIdValueGenerator defineEntityCustomValueGenerator(String className) {

        try {
            Class<?> klass = Thread.currentThread().getContextClassLoader().loadClass(className);
            return customValueGeneratingComponent.defineEntityCustomValueGenerator(klass);
        } catch (ClassNotFoundException e) {
            LOGGER.error("Failed to load custom entity id generating class for name [{}].", className, e);
        }

        return null;
    }
}
